import { NesUtil } from "."
import { Tile } from "./PPU/Tile";

/** 镜像 */
export enum MIRRORING {
    /** 垂直 */ VERTICAL,
    /** 水平 */ HORIZONTAL,
    /** 四个屏幕 */ FOURSCREEN,
    /** 单一屏幕 */ SINGLESCREEN,
    /** 单一屏幕2 */ SINGLESCREEN2,
    /** 单一屏幕3 */ SINGLESCREEN3,
    /** 单一屏幕4 */ SINGLESCREEN4,
    /** CHR ROM */ CHRROM,
}

export class ROM {

    private nes: NesUtil;

    private mapperName: string[] = new Array(92).fill("Unknown Mapper");

    constructor(nes: NesUtil) {
        this.nes = nes;

        this.mapperName[0] = "Direct Access";
        this.mapperName[1] = "Nintendo MMC1";
        this.mapperName[2] = "UNROM";
        this.mapperName[3] = "CNROM";
        this.mapperName[4] = "Nintendo MMC3";
        this.mapperName[5] = "Nintendo MMC5";
        this.mapperName[6] = "FFE F4xxx";
        this.mapperName[7] = "AOROM";
        this.mapperName[8] = "FFE F3xxx";
        this.mapperName[9] = "Nintendo MMC2";
        this.mapperName[10] = "Nintendo MMC4";
        this.mapperName[11] = "Color Dreams Chip";
        this.mapperName[12] = "FFE F6xxx";
        this.mapperName[15] = "100-in-1 switch";
        this.mapperName[16] = "Bandai chip";
        this.mapperName[17] = "FFE F8xxx";
        this.mapperName[18] = "Jaleco SS8806 chip";
        this.mapperName[19] = "Namcot 106 chip";
        this.mapperName[20] = "Famicom Disk System";
        this.mapperName[21] = "Konami VRC4a";
        this.mapperName[22] = "Konami VRC2a";
        this.mapperName[23] = "Konami VRC2a";
        this.mapperName[24] = "Konami VRC6";
        this.mapperName[25] = "Konami VRC4b";
        this.mapperName[32] = "Irem G-101 chip";
        this.mapperName[33] = "Taito TC0190/TC0350";
        this.mapperName[34] = "32kB ROM switch";

        this.mapperName[64] = "Tengen RAMBO-1 chip";
        this.mapperName[65] = "Irem H-3001 chip";
        this.mapperName[66] = "GNROM switch";
        this.mapperName[67] = "SunSoft3 chip";
        this.mapperName[68] = "SunSoft4 chip";
        this.mapperName[69] = "SunSoft5 FME-7 chip";
        this.mapperName[71] = "Camerica chip";
        this.mapperName[78] = "Irem 74HC161/32-based";
        this.mapperName[91] = "Pirate HK-SF3 chip";
    }

    private header: Uint8Array = null;
    public rom: Uint8Array[] = null;
    public vrom: Uint8Array[] = null;
    public vromTile: Tile[][] = null;

    public romCount: number = null;
    public vromCount: number = null;
    private mirroring: number = null;
    public batteryRam: number[] = null;
    private trainer: boolean = null;
    private fourScreen: boolean = null;
    private mapperType: MIRRORING = null;
    public valid: boolean = false;

    load(data: Uint8Array) {
        // NES\x1a
        if (data[0] !== 0x4E || data[1] !== 0x45 || data[2] !== 0x53 || data[3] !== 0x1A) {
            this.nes.opts.updateStatus("Not a valid NES ROM.");
            return;
        }
        this.header = new Uint8Array(16);
        for (let i = 0; i < 16; i++) {
            this.header[i] = data[i] & 0xFF;
        }
        this.romCount = this.header[4];
        this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB
        this.mirroring = ((this.header[6] & 1) !== 0 ? 1 : 0);
        const batteryRam = (this.header[6] & 2) !== 0;
        this.trainer = (this.header[6] & 4) !== 0;
        this.fourScreen = (this.header[6] & 8) !== 0;
        this.mapperType = (this.header[6] >> 4) | (this.header[7] & 0xF0);
        /* TODO
        if (batteryRam)
            this.loadBatteryRam();*/

        // Check whether byte 8-15 are zero's:
        let foundError = false;
        for (let i = 8; i < 16; i++) {
            if (this.header[i] !== 0) {
                foundError = true;
                break;
            }
        }
        if (foundError) {
            this.mapperType &= 0xF; // Ignore byte 7
        }

        let offset = 16;

        // Load PRG-ROM banks:
        const romSize = 64 * 64 * 4;
        this.rom = new Array(this.romCount);
        for (let i = 0; i < this.romCount; i++) {
            this.rom[i] = new Uint8Array(romSize);
            for (let j = 0; j < romSize; j++) {
                if (offset + j >= data.length) {
                    break;
                }
                this.rom[i][j] = data[offset + j] & 0xFF;
            }
            offset += romSize;
        }

        // Load CHR-ROM banks:
        const vromSize = 64 * 64;
        this.vrom = new Array(this.vromCount);
        for (let i = 0; i < this.vromCount; i++) {
            this.vrom[i] = new Uint8Array(vromSize);
            for (let j = 0; j < vromSize; j++) {
                if (offset + j >= data.length) {
                    break;
                }
                this.vrom[i][j] = data[offset + j] & 0xFF;
            }
            offset += vromSize;
        }

        // Create VROM tiles:
        const vromTileSize = 64 * 4;
        this.vromTile = new Array(this.vromCount);
        for (let i = 0; i < this.vromCount; i++) {
            this.vromTile[i] = new Array(vromTileSize);
            for (let j = 0; j < vromTileSize; j++) {
                this.vromTile[i][j] = new Tile();
            }
        }

        // Convert CHR-ROM banks to tiles:
        let tileIndex: number;
        let leftOver: number;
        for (let v = 0; v < this.vromCount; v++) {
            for (let i = 0; i < vromSize; i++) {
                tileIndex = i >> 4;
                leftOver = i % 16;
                if (leftOver < 8) {
                    this.vromTile[v][tileIndex].setScanline(
                        leftOver,
                        this.vrom[v][i],
                        this.vrom[v][i + 8]
                    );
                }
                else {
                    this.vromTile[v][tileIndex].setScanline(
                        leftOver - 8,
                        this.vrom[v][i - 8],
                        this.vrom[v][i]
                    );
                }
            }
        }
        this.valid = true;
    }

    getMirroringType() {
        if (this.fourScreen) {
            return MIRRORING.FOURSCREEN;
        } else if (this.mirroring === 0) {
            return MIRRORING.HORIZONTAL;
        }
        return MIRRORING.VERTICAL;
    }

    getMapperName() {
        if (this.mapperType >= 0 && this.mapperType < this.mapperName.length) {
            return this.mapperName[this.mapperType];
        }
        return "Unknown Mapper, " + this.mapperType;
    }

    mapperSupported() {
            return typeof NesUtil.Mappers[this.mapperType] !== 'undefined';
    }

    createMapper() {
        if (this.mapperSupported()) {
            return new NesUtil.Mappers[this.mapperType](this.nes);
        } else {
            const message = "This ROM uses a mapper not supported by JSNES: " + this.getMapperName() + "(" + this.mapperType + ")";
            this.nes.opts.updateStatus(message);
            throw new Error(message);
            return null;
        }
    }
}